/* weather
Michael Zahniser, 30 May 2012

Console program to download, parse, and print the forecast from weather.gov.
Requires libexpat1-dev and libcurl4-gnutls-dev.
compile with: $ g++ weather.cpp -o weather -lexpat `curl-config --libs`

TODO: Handle and report error conditions (download failed, etc.).
*/

#include <iostream>
#include <string>
#include <vector>
#include <ctime>
#include <algorithm>

#include <expat.h>
#include <curl/curl.h>

using namespace std;

namespace {
	// Global variables. Yes, I know it's bad style.
	static const string DATE = "start-valid-time";
	static const string TEMP = "temperature";
	static const string WEATHER = "weather-conditions";
	static const string VALUE = "value";
	
	static const string NONE = "none";
	static const string ADDITIVE = "additive";
	
	bool isDate = false;
	bool isTemp = false;
	bool isWeather = false;
	bool isValue = false;
	
	vector<string> date;
	vector<string> temp;
	vector<string> weather;
}

// Receiving data downloaded by CURL.
size_t receive(void *text, size_t size, size_t count, void *userData);

// Handling XML data.
void start(void *, const char *element, const char **attr);
void chars(void *, const char *text, int length);
void end(void *, const char *element);

// Formatting date strings.
string Date(const string &text);
string Diff(const string &previous, const string &current);



int main(int argc, char *argv[])
{
	// Boston, MA.
	string lat = argc > 1 ? argv[1] : "42.3";
	string lon = argc > 2 ? argv[2] : "-71.1";
	string url = "http://graphical.weather.gov/xml/sample_products/browser_interface/ndfdXMLclient.php?lat="
		+ lat + "&lon=" + lon + "&product=time-series&temp=temp&wx=wx";
	
	// Create an XML parser to process the input.
	XML_Parser p = XML_ParserCreate(NULL);
	
	XML_SetElementHandler(p, start, end);
	XML_SetCharacterDataHandler(p, chars);
	
	// Download the input and pass it directly to the parser.
	curl_global_init(CURL_GLOBAL_ALL);
	CURL *curl = curl_easy_init();
	
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &receive);
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, &p);
	curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
	curl_easy_perform(curl);
	
	curl_easy_cleanup(curl);
	curl_global_cleanup();
	
	// Tell the parser it is done.
	XML_Parse(p, "", 0, true);
	XML_ParserFree(p);
	
	// Format the date strings.
	string previous;
	for(unsigned i = 0; i < date.size(); ++i)
	{
		string current = Date(date[i]);
		date[i] = Diff(previous, current);
		previous.swap(current);
	}
	
	// Print the data.
	unsigned entries = min(min(date.size(), temp.size()), weather.size());
	for(unsigned i = entries; i--; )
		cout << date[i] << ": " << temp[i] << "° " << weather[i] << endl;
	
	return 0;
}



size_t receive(void *text, size_t size, size_t count, void *userData)
{
	size *= count;
	
	XML_Parser &p = *reinterpret_cast<XML_Parser *>(userData);
	XML_Parse(p, reinterpret_cast<const char *>(text), size, false);
	
	return size;
}



void start(void *, const char *element, const char **attr)
{
	if(element == DATE)
	{
		date.push_back("");
		isDate = true;
	}
	if(element == TEMP)
		isTemp = true;
	else if(element == WEATHER)
	{
		weather.push_back("");
		isWeather = true;
	}
	else if(element == VALUE)
	{
		if(isTemp)
		{
			temp.push_back("");
			isValue = true;
		}
		else if(isWeather)
		{
			if(!weather.back().empty())
				weather.back() += ',';
			for(const char **it = attr; *it; it += 2)
				if(it[1] != NONE && it[0] != ADDITIVE)
				{
					if(!weather.back().empty())
						weather.back() += ' ';
					weather.back().append(it[1]);
				}
		}
	}
}



void chars(void *, const char *text, int length)
{
	if(isValue)
		temp.back().append(text, length);
	else if(isDate)
		date.back().append(text, length);
}



void end(void *, const char *element)
{
	isDate &= (element != DATE);
	isTemp &= (element != TEMP);
	isWeather &= (element != WEATHER);
	isValue &= (element != VALUE);
}



string Date(const string &text)
{
	static tm t;
	strptime(text.c_str(), "%FT%T", &t);
	static char buffer[32];
	size_t len = strftime(buffer, 32, "%a %e %b %l %P", &t);
	return string(buffer, len);
}



string Diff(const string &previous, const string &current)
{
	static const int PREFIX = 11;
	if(previous.compare(0, PREFIX, current, 0, PREFIX))
		return current;
	
	string result(PREFIX, ' ');
	result.append(current, PREFIX, string::npos);
	return result;
}

